package com.wiscamp.ninechapters.problems.infrastructure.repositories;

import com.wiscamp.ninechapters.problems.domain.ProblemsErrorCodes;
import com.wiscamp.ninechapters.problems.domain.models.collections.CollectionProblem;
import com.wiscamp.ninechapters.problems.domain.models.problems.answers.Answer;
import com.wiscamp.ninechapters.problems.domain.models.problems.problems.Problem;
import com.wiscamp.ninechapters.problems.domain.models.problems.problems.Stem;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Option;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Question;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Solution;
import com.wiscamp.ninechapters.problems.domain.repositories.ProblemCollectionRepository;
import com.wiscamp.ninechapters.problems.domain.repositories.ProblemTypeRepository;
import com.wiscamp.ninechapters.problems.domain.repositories.QuestionTypeRepository;
import com.wiscamp.ninechapters.problems.enums.Actions;
import com.wiscamp.ninechapters.problems.enums.AnswerTypes;
import com.wiscamp.ninechapters.problems.infrastructure.converters.ProblemConverter;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.collections.CollectionProblemMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.collections.ProblemCollectionMapper;
import com.wiscamp.ninechapters.problems.infrastructure.mappers.problems.*;
import com.wiscamp.ninechapters.problems.infrastructure.po.collections.CollectionProblemPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.collections.ProblemCollectionPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.exams.ExamPO;
import com.wiscamp.ninechapters.problems.infrastructure.po.problems.*;
import cube.ddd.aspect.HandleDataException;
import cube.ddd.exception.ExceptionUtils;
import cube.ddd.exception.ServiceException;
import cube.ddd.utils.PersistenceUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;

/**
 * 题访问器 （负责题实体至题PO的转换、负责题PO的读取及存储）
 * @author maxdong
 */
@Component
public class ProblemAccessor {

    public final Logger logger = LoggerFactory.getLogger(this.getClass());

    public final ProblemMapper problemMapper;

    public final ProblemViewMapper problemViewMapper;

    public final CollectionProblemMapper collectionProblemMapper;

    public final ProblemCollectionMapper problemCollectionMapper;

    public final StemMapper stemMapper;

    public final QuestionMapper questionMapper;

    public final QuestionViewMapper questionViewMapper;

    public final QuestionSolutionMapper questionSolutionMapper;

    public final QuestionOptionMapper questionOptionMapper;

    public final QuestionAnswerMapper questionAnswerMapper;

    public final ProblemTypeRepository problemTypeRepository;

    public final QuestionTypeRepository questionTypeRepository;

    public final ProblemCollectionRepository problemCollectionRepository;

    public ProblemAccessor(ProblemMapper problemMapper, ProblemViewMapper problemViewMapper, CollectionProblemMapper collectionProblemMapper, ProblemCollectionMapper problemCollectionMapper, StemMapper stemMapper, QuestionMapper questionMapper, QuestionViewMapper questionViewMapper, QuestionSolutionMapper questionSolutionMapper, QuestionOptionMapper questionOptionMapper, QuestionAnswerMapper questionAnswerMapper, ProblemTypeRepository problemTypeRepository, QuestionTypeRepository questionTypeRepository, ProblemCollectionRepository problemCollectionRepository) {
        this.problemMapper = problemMapper;
        this.problemViewMapper = problemViewMapper;
        this.collectionProblemMapper = collectionProblemMapper;
        this.problemCollectionMapper = problemCollectionMapper;
        this.stemMapper = stemMapper;
        this.questionMapper = questionMapper;
        this.questionViewMapper = questionViewMapper;
        this.questionSolutionMapper = questionSolutionMapper;
        this.questionOptionMapper = questionOptionMapper;
        this.questionAnswerMapper = questionAnswerMapper;
        this.problemTypeRepository = problemTypeRepository;
        this.questionTypeRepository = questionTypeRepository;
        this.problemCollectionRepository = problemCollectionRepository;
    }

    // region convert PO
    public ProblemPO toProblemPO(@NotNull Problem problem) {
        var problemPO = ProblemConverter.Instance.toProblemPO(problem);

        // stem
        if (problem.isHasStem() && Objects.nonNull(problem.getCurrentStem())) {
            var stemPO = toStemPO(problem.getCurrentStem());
            problemPO.setStem(stemPO);
        }

        // questions
        Collection<Question> questions = new ArrayList<>();
        if (problem.isHasMultipleQuestions()) {
            questions = problem.getCurrentQuestions();
        } else {
            questions.add(problem.getCurrentQuestion());
        }

        for(var question : questions) {
            var questionPO = toQuestionPO(question);
            problemPO.getQuestions().add(questionPO);
        }
        return problemPO;
    }

    public StemPO toStemPO(@NotNull Stem stem) {
        return ProblemConverter.Instance.toStemPO(stem);
    }

    public QuestionPO toQuestionPO(@NotNull Question question) {
        var questionPO = ProblemConverter.Instance.toQuestionPO(question);
        questionPO.getSolutions().clear();

        // options
        if (question.isHasOptions()) {
            var options = question.getCurrentOptions();
            for (var option : options) {
                var optionPO = toOptionPO(option);
                questionPO.getOptions().add(optionPO);
            }
        }

        // answers
        /*if (question.isHasMultipleAnswers()) {
            var answers = question.getCurrentAnswers();
            for (var answer : answers) {
                var answerPO = toAnswerPO(answer);
                questionPO.getAnswers().add(answerPO);
            }
        } else {
            var answer = question.getCurrentAnswer();
            if (Objects.nonNull(answer)) {
                var answerPO = toAnswerPO(answer);
                questionPO.getAnswers().add(answerPO);
            }
        }*/

        if (question.getCurrentAnswers().isEmpty()) {
            var answer = question.getCurrentAnswer();
            if (Objects.nonNull(answer)) {
                var answerPO = toAnswerPO(answer);
                questionPO.getAnswers().add(answerPO);
            }
        } else {
            var answers = question.getCurrentAnswers();
            for (var answer : answers) {
                var answerPO = toAnswerPO(answer);
                questionPO.getAnswers().add(answerPO);
            }
        }


        // solutions
        var solutions = question.getSolutions();
        for (var solution : solutions) {
            var solutionPO = toSolutionPO(solution);
            questionPO.getSolutions().add(solutionPO);
        }

        return questionPO;
    }

    public QuestionAnswerPO toAnswerPO(@NotNull Answer answer) {
        return ProblemConverter.Instance.toAnswerPO(answer);
    }

    public QuestionOptionPO toOptionPO(@NotNull Option option) {
        return ProblemConverter.Instance.toOptionPO(option);
    }

    public QuestionSolutionPO toSolutionPO(@NotNull Solution solution) {
        return ProblemConverter.Instance.toSolutionPO(solution);
    }
    // endregion

    //region stem
    @HandleDataException
    public StemPO getStemPO(@Min(0) long stemId) {
        return PersistenceUtils.findById(stemId, stemMapper::findById);
    }

    @HandleDataException
    public StemPO getStemPOByProblem(@Min(0) long problemId) {
        return PersistenceUtils.getById(problemId, stemMapper::findByProblem);
    }

    public StemPO processStemPO(@NotNull StemPO stemPO) {
        StemPO processedStem = null;
        switch (stemPO.getAction()) {
            case CREATE -> processedStem = createStemPO(stemPO);
            case UPDATE -> processedStem = updateStemPO(stemPO);
            case DELETE -> deleteStemPO(stemPO.getStemId());
            default -> logger.error("无法处理题干。");
        }
        return processedStem;
    }

    public StemPO createStemPO(@NotNull StemPO stemPO) {
        return stemMapper.saveAndFlush(stemPO);
    }

    public StemPO updateStemPO(@NotNull StemPO stemPO) {
        return stemMapper.saveAndFlush(stemPO);
    }

    private boolean deleteStemPO(@Min(0) long stemId) {
        return PersistenceUtils.delete(stemId, stemMapper::deleteById);
    }
    //endregion

    //region question
    @HandleDataException
    public Collection<QuestionPO> getQuestionPOs(@Min(0) long problemId) {
        var questionPOs = PersistenceUtils.getListById(problemId, questionMapper::findListByProblem);
        for (var questionPO : questionPOs) {
            var hyperText = questionPO.getQuestionHypertext();
            hyperText = hyperText.replace("&amp;", "&");
            questionPO.setQuestionHypertext(hyperText);
            var questionType = questionTypeRepository.getQuestionType(questionPO.getQuestionTypeId());
            if (Objects.isNull(questionType)) {
                logger.error("Failed to find the question type information. The question type id is {}.", questionPO.getQuestionTypeId());
                return new ArrayList<>();
            }
            loadQuestionDetails(questionPO);
        }
        return questionPOs;
    }

    @HandleDataException
    public Collection<QuestionView> getQuestionViews(long problemId) {
        return PersistenceUtils.getListById(problemId, questionViewMapper::findListByProblem);
    }

    @HandleDataException
    public QuestionPO getFirstQuestion(long problemId) {
        var questionPOs = getQuestionPOs(problemId);
        if (questionPOs.isEmpty()) return null;
        return questionPOs.stream().findFirst().orElse(null);
    }

    @HandleDataException
    public QuestionView getFirstQuestionView(long problemId) {
        var questionViews = getQuestionViews(problemId);
        if (questionViews.isEmpty()) return null;
        return questionViews.stream().findFirst().orElse(null);
    }

    @HandleDataException
    public QuestionView getQuestionView(long questionId) {
        return PersistenceUtils.findById(questionId, questionViewMapper::findById);
    }

    @HandleDataException
    public QuestionPO getQuestion(long questionId) {
        var questionPO = PersistenceUtils.findById(questionId, questionMapper::findById);
        if (Objects.isNull(questionPO)) return null;
        loadQuestionDetails(questionPO);
        return questionPO;
    }

    private void loadQuestionDetails(@NotNull QuestionPO questionPO) {
        long questionId = questionPO.getQuestionId();
        long questionTypeId = questionPO.getQuestionTypeId();
        var questionType = questionTypeRepository.getQuestionType(questionTypeId);
        if (Objects.isNull(questionType)) {
            logger.error("获取问题类型信息失败。 问题类型编号: {}", questionPO.getQuestionTypeId());
            return;
        }

        // load solutions
        var solutions = getSolutionPOs(questionId);
        questionPO.setSolutions(solutions);

        // load options
        if (questionType.isHasOption()) {
            var options = getOptionPOs(questionId);
            questionPO.setOptions(options);
        }

        // load answers
        var answers = getAnswerPOs(questionId);
        questionPO.setAnswers(answers);
    }

    private void loadQuestionViewDetails(@NotNull QuestionView questionView) {
        long questionId = questionView.getQuestionId();
        long questionTypeId = questionView.getQuestionTypeId();
        var questionType = questionTypeRepository.getQuestionType(questionTypeId);
        if (Objects.isNull(questionType)) {
            logger.error("获取问题类型信息失败。 问题类型编号: {}", questionView.getQuestionTypeId());
        }

        // load solutions
        var solutions = getSolutionPOs(questionId);
        questionView.setSolutions(solutions);

        // load options
        if (questionType.isHasOption()) {
            var options = getOptionPOs(questionId);
            questionView.setOptions(options);
        }

        // load answers
        var answers = getAnswerPOs(questionId);
        questionView.setAnswers(answers);
    }

    public QuestionPO processQuestionPO(@NotNull QuestionPO questionPO) {
        QuestionPO processedQuestion = null;
        switch (questionPO.getAction()) {
            case CREATE:
                processedQuestion = createQuestionPO(questionPO);
                break;
            case UPDATE:
                processedQuestion = updateQuestionPO(questionPO);
                break;
            case DELETE:
                deleteQuestionPO(questionPO.getQuestionId());
                break;
            case NONE:
                break;
            default:
                logger.debug("无法处理问题");
                break;
        }
        return processedQuestion;
    }

    public boolean deleteQuestionPO(@Min(0) long questionId) {
        questionAnswerMapper.deleteByQuestion(questionId);
        questionSolutionMapper.deleteByQuestion(questionId);
        questionOptionMapper.deleteByQuestion(questionId);
        questionMapper.deleteById(questionId);
        return true;
    }

    public QuestionPO createQuestionPO(@NotNull QuestionPO questionPO) {
        var newQuestionPO = questionMapper.saveAndFlush(questionPO);
        long problemId = questionPO.getProblemId();
        long newQuestionId = newQuestionPO.getQuestionId();

        // save solutions
        for (var solutionPO : questionPO.getSolutions()) {
            solutionPO.setProblemId(problemId);
            solutionPO.setQuestionId(newQuestionId);
            // 如果只有一个solution，强制设置未优选solution
            if (questionPO.getSolutions().size() == 1 && !solutionPO.isPreferred()) {
                solutionPO.setPreferred(true);
            }
            questionSolutionMapper.saveAndFlush(solutionPO);
        }

        var options = questionPO.getOptions();
        var answers = questionPO.getAnswers();

        if (options.size() > 0) {
            // 如果是单选或多选题，正确答案直接从Options中创建
            var correctOptionIds = new ArrayList<Long>();
            var correctOptionMap = new HashMap<Long, Integer>();
            for (var option : options) {
                option.setProblemId(problemId);
                option.setQuestionId(newQuestionId);
                var newOption = questionOptionMapper.saveAndFlush(option);
                if (newOption.isCorrectAnswer()) {
                    correctOptionIds.add(newOption.getOptionId());
                    correctOptionMap.put(newOption.getOptionId(), newOption.getOptionIndex());
                }
            }

            if (correctOptionIds.size() <= 0) {
                logger.error("未能发现正确选项。");
            } else {
                var optionAnswer = new QuestionAnswerPO();
                optionAnswer.setQuestionId(newQuestionId);
                optionAnswer.setProblemId(problemId);

                if (correctOptionIds.size() > 1) {
                    // 处理多选问题的多个正确答案
                    optionAnswer.setAnswerTypeId(AnswerTypes.MULTIPLE_OPTION.getValue());
                    optionAnswer.setAnswerOptionId(0);
                    var sortedOptionIds = correctOptionIds.stream().sorted().toList();
                    var text = StringUtils.join(sortedOptionIds, ",");
                    optionAnswer.setAnswerHypertext(text);
                } else {
                    // 处理单选问题的唯一正确答案
                    optionAnswer.setAnswerTypeId(AnswerTypes.OPTION.getValue());
                    var correctOptionId = (Long) correctOptionIds.toArray()[0];
                    optionAnswer.setAnswerOptionId(correctOptionId);
                    optionAnswer.setAnswerHypertext(String.valueOf(correctOptionId));
                }
                optionAnswer.setAnswerIndex(1);
                questionAnswerMapper.saveAndFlush(optionAnswer);
            }
        } else {
            // 处理非单选多选问题的正确答案
            for (var answer : answers) {
                answer.setProblemId(problemId);
                answer.setQuestionId(newQuestionId);
                questionAnswerMapper.saveAndFlush(answer);
            }
        }
        return getQuestion(newQuestionId);
    }

    public QuestionPO updateQuestionPO(QuestionPO questionPO) {
        var updatedQuestionPO = questionMapper.saveAndFlush(questionPO);

        // 修改问题时，Solutions可以新增、删除、修改
        for (var solutionPO : questionPO.getSolutions()) {
            solutionPO.setProblemId(updatedQuestionPO.getProblemId());
            solutionPO.setQuestionId(updatedQuestionPO.getQuestionId());
            processSolutionPO(solutionPO);
        }

        // 修改问题时，Options无法创建、删除，仅能修改
        var options = questionPO.getOptions();
        for (var optionPO : options) {
            optionPO.setProblemId(updatedQuestionPO.getProblemId());
            optionPO.setQuestionId(updatedQuestionPO.getQuestionId());
            optionPO.setAction(Actions.UPDATE);
            processOptionPO(optionPO);
        }

        // 修改问题时，Answers可以新增、删除、修改
        // todo: 选择题，需要根据选项修改答案
        var answers = questionPO.getAnswers();
        for (var answer : answers) {
            answer.setProblemId(updatedQuestionPO.getProblemId());
            answer.setQuestionId(updatedQuestionPO.getQuestionId());
            processAnswerPO(answer);
        }

        //reload question
        return getQuestion(updatedQuestionPO.getQuestionId());
    }
    //endregion

    //region solution
    @HandleDataException
    public QuestionSolutionPO getSolutionPO(@Min(0) long solutionId) {
        return PersistenceUtils.findById(solutionId, questionSolutionMapper::findById);
    }

    @HandleDataException
    public QuestionSolutionPO getPreferredSolutionPO(@Min(0) long questionId) {
        var solutionPOs = getSolutionPOs(questionId);
        var optionalSolutionPO = solutionPOs.stream().filter(s -> s.isPreferred()).findFirst();
        return optionalSolutionPO.orElse(null);
    }

    @HandleDataException
    public Collection<QuestionSolutionPO> getSolutionPOs(@Min(0) long questionId) {
        return PersistenceUtils.getById(questionId, questionSolutionMapper::findListByQuestion);
    }

    public QuestionSolutionPO processSolutionPO(@NotNull QuestionSolutionPO solutionPO) {
        QuestionSolutionPO processedSolutionPO = null;
        switch (solutionPO.getAction()) {
            case CREATE:
                processedSolutionPO = createSolutionPO(solutionPO);
                break;
            case UPDATE:
                processedSolutionPO = updateSolutionPO(solutionPO);
                break;
            case DELETE:
                deleteSolutionPO(solutionPO.getSolutionId());
                break;
            case NONE:
                break;
            default:
                logger.debug("无法处理解析信息。");
                break;
        }
        return processedSolutionPO;
    }

    private QuestionSolutionPO createSolutionPO(@NotNull QuestionSolutionPO solutionPO) {
        return PersistenceUtils.save(solutionPO, questionSolutionMapper::saveAndFlush);
    }

    private QuestionSolutionPO updateSolutionPO(@NotNull QuestionSolutionPO solutionPO) {
        return PersistenceUtils.save(solutionPO, questionSolutionMapper::saveAndFlush);
    }

    private boolean deleteSolutionPO(@Min(0) long solutionId) {
        return PersistenceUtils.delete(solutionId, questionSolutionMapper::deleteById);
    }

    //endregion

    //region answer
    @HandleDataException
    public QuestionAnswerPO getAnswerPO(@Min(0) long answerId) {
        return PersistenceUtils.findById(answerId, questionAnswerMapper::findById);
    }

    @HandleDataException
    public QuestionAnswerPO getAnswerPO(@NotEmpty Collection<QuestionAnswerPO> answers, @Min(0) long answerId) {
        var optionalAnswerPO = answers.stream().filter(a -> a.getAnswerId() == answerId).findFirst();
        return optionalAnswerPO.orElse(null);
    }

    @HandleDataException
    public QuestionAnswerPO getFirstAnswerPO(long questionId) {
        var answerPOs = getAnswerPOs(questionId);
        return answerPOs.stream().findFirst().orElse(null);
    }

    @HandleDataException
    public QuestionAnswerPO getFirstAnswerPO(@NotEmpty Collection<QuestionAnswerPO> answers) {
        return answers.stream().findFirst().orElse(null);
    }

    @HandleDataException
    public Collection<QuestionAnswerPO> getAnswerPOs(@Min(0) long questionId) {
        return PersistenceUtils.getListById(questionId, questionAnswerMapper::findListByQuestion);
    }

    public QuestionAnswerPO processAnswerPO(@NotNull QuestionAnswerPO answerPO) {
        QuestionAnswerPO processedAnswer = null;
        switch (answerPO.getAction()) {
            case CREATE:
                processedAnswer = createAnswerPO(answerPO);
                break;
            case UPDATE:
                processedAnswer = updateAnswerPO(answerPO);
                break;
            case DELETE:
                deleteAnswerPO(answerPO.getAnswerId());
                break;
            case NONE:
                break;
            default:
                logger.debug("无法处理答案信息。");
                break;
        }
        return processedAnswer;
    }

    private QuestionAnswerPO createAnswerPO(@NotNull QuestionAnswerPO answerPO) {
        return PersistenceUtils.save(answerPO, questionAnswerMapper::saveAndFlush);
    }

    private QuestionAnswerPO updateAnswerPO(@NotNull QuestionAnswerPO answerPO) {
        return PersistenceUtils.save(answerPO, questionAnswerMapper::saveAndFlush);
    }

    private boolean deleteAnswerPO(@Min(0) long answerId) {
        return PersistenceUtils.delete(answerId, questionAnswerMapper::deleteById);
    }
    //endregion

    //region option
    @HandleDataException
    public QuestionOptionPO getOptionPO(long optionId) {
        return PersistenceUtils.findById(optionId, questionOptionMapper::findById);
    }

    @HandleDataException
    public Collection<QuestionOptionPO> getOptionPOs(@Min(0) long questionId) {
        return PersistenceUtils.getListById(questionId, questionOptionMapper::findListByQuestion);
    }

    public QuestionOptionPO processOptionPO(@NotNull QuestionOptionPO optionPO) {
        QuestionOptionPO processedOption = null;
        switch (optionPO.getAction()) {
            case CREATE:
                processedOption = createOptionPO(optionPO);
                break;
            case UPDATE:
                processedOption = updateOptionPO(optionPO);
                break;
            case DELETE:
                deleteOptionPO(optionPO.getOptionId());
                break;
            case NONE:
                break;
            default:
                logger.debug("无法处理选项信息。");
                break;
        }
        return processedOption;
    }

    private QuestionOptionPO createOptionPO(@NotNull QuestionOptionPO optionPO) {
        return PersistenceUtils.save(optionPO, questionOptionMapper::saveAndFlush);
    }

    private QuestionOptionPO updateOptionPO(@NotNull QuestionOptionPO optionPO) {
        return PersistenceUtils.save(optionPO, questionOptionMapper::saveAndFlush);
    }

    private boolean deleteOptionPO(@Min(0) long optionId) {
        return PersistenceUtils.delete(optionId, questionOptionMapper::deleteById);
    }

    @HandleDataException
    public int getOptionIndex(@NotEmpty Collection<QuestionOptionPO> options, @Min(0) long optionId) {
        var optionalOptionPO = options.stream().filter(o -> o.getOptionId() == optionId).findFirst();
        return optionalOptionPO.map(QuestionOptionPO::getOptionIndex).orElse(-1);

    }
    //endregion

    // region problem
    @HandleDataException
    public ProblemView getProblemView(long problemId) {
        return PersistenceUtils.findById(problemId, problemViewMapper::findById);
    }

    @HandleDataException
    public ProblemPO getProblemPO(@Min(0) long problemId) {
        var problemPO = PersistenceUtils.findById(problemId, problemMapper::findById);
        if (Objects.isNull(problemPO)) return null;
        // 加载题干
        loadStemPO(problemPO);
        // 加载问题
        loadQuestionPOs(problemPO);
        return problemPO;
    }

    @HandleDataException
    public Collection<ProblemPO> getProblemPOs(@NotEmpty Collection<Long> problemIds) {
        Collection<ProblemPO> problems = new ArrayList<>();
        problemIds.forEach(id -> problems.add(getProblemPO(id)));
        return problems;
    }

    @HandleDataException
    public ProblemPO createProblemPO(@NotNull ProblemPO problemPO) {
        return problemMapper.saveAndFlush(problemPO);
    }

    @HandleDataException
    public ProblemPO updateProblemPO(@NotNull ProblemPO problemPO) {
        return problemMapper.saveAndFlush(problemPO);
    }

    @HandleDataException
    public boolean validateProblemPO(@Min(0) long problemId) {
        var problemPO = getProblemPO(problemId);
        if (Objects.isNull(problemPO)) {
            var errorCode = "service-exception.item.id.notfound";
            throw new ServiceException(null, errorCode, problemId);
        }
        ProblemCollectionPO collectionPO;
        var collectionProblemPO = collectionProblemMapper.findByProblem(problemId);
        if (Objects.isNull(collectionProblemPO)) {
            var errorCode = "service-exception.pool-item.id.notfound";
            throw new ServiceException(null, errorCode, problemId);
        } else {
            // validate pool
            long collectionId = collectionProblemPO.getCollectionId();
            var problemCollectionPO = problemCollectionMapper.findById(collectionId);
            if (Objects.isNull(problemCollectionPO)) {
                var errorCode = "service-exception.pool-item.id.notfound";
                throw new ServiceException(null, errorCode, problemId);
            }
        }

        ExamPO examPO;

        // validate item type
        long problemTypeId = problemPO.getProblemTypeId();
        var problemType = problemTypeRepository.getProblemType(problemTypeId);
        if (Objects.isNull(problemType)) {
            var errorCode = "service-exception.item-type.id.notfound";
            throw new ServiceException(null, errorCode, problemTypeId);
        }
        // validate stem
        boolean hasStem = problemType.isHasStem();
        if (hasStem && problemPO.getStem() == null || !hasStem && problemPO.getStem() != null) {
            var errorCode = "service-exception.item.stem.notmatch";
            throw new ServiceException(null, errorCode, problemId);
        }
        // validate question quantity
        Collection<QuestionPO> questions = problemPO.getQuestions();
        if (questions.size() <= 0) {
            var errorCode = "service-exception.question.quantity.zero";
            throw new ServiceException(null, errorCode, problemId);
        }

        for (QuestionPO q : questions) {
            // validate question type
            long questionTypeId = q.getQuestionTypeId();
            var questionType = questionTypeRepository.getQuestionType(questionTypeId);
            if (questionType == null) {
                var errorCode = "service-exception.question-type.id.notfound";
                throw new ServiceException(null, errorCode, problemId);
            }
            // QuestionAnalysis solution = q.getAnalysis();
            // validate answers and options
            Collection<QuestionAnswerPO> answers = q.getAnswers();
            int correctOptionQty = 0;
            if (questionType.isHasOption()) {
                Collection<QuestionOptionPO> options = q.getOptions();
                if (questionType.getOptionQuantity() != options.size()) {
                    var errorCode = "service-exception.question.option.quantity.notequal";
                    throw new ServiceException(null, errorCode, problemId);
                }
                for (var o : options) {
                    if (o.isCorrectAnswer()) {
                        correctOptionQty++;
                        boolean foundAnswer = false;
                        for (var a : answers) {
                            if (a.getQuestionId() == o.getQuestionId() && o.getOptionId() == a.getAnswerOptionId()) {
                                foundAnswer = true;
                                break;
                            }
                        }
                        if (!foundAnswer) {
                            var errorCode = "service-exception.question.answer.notmatch.option";
                            throw new ServiceException(null, errorCode, problemId);
                        }
                    }
                }
            }
            if (correctOptionQty != answers.size()) {
                var errorCode = "service-exception.question.answer.option.notmatch";
                throw new ServiceException(null, errorCode, problemId);
            }
        }
        return true;
    }
    //endregion

    // region collection problem
    @HandleDataException
    public CollectionProblemPO createCollectionProblem(@NotNull CollectionProblemPO collectionProblemPO) {
        return PersistenceUtils.save(collectionProblemPO, collectionProblemMapper::saveAndFlush);
    }

    // endregion

    //region load problem details
    public void loadStemPO(@NotNull ProblemPO problem) {
        StemPO stemPO = getStemPOByProblem(problem.getProblemId());
        if (Objects.nonNull(stemPO))
            problem.setStem(stemPO);
    }

    public void loadStemPO(@NotNull ProblemView problem) {
        StemPO stemPO = getStemPOByProblem(problem.getProblemId());
        if (Objects.nonNull(stemPO))
            problem.setStem(stemPO);
    }

    public void loadQuestionPOs(@NotNull ProblemPO problem) {
        var questions = getQuestionPOs(problem.getProblemId());
        for (var questionPO : questions) {
            loadQuestionDetails(questionPO);
        }
        problem.setQuestions(questions);
    }

    public void loadQuestionPOs(@NotNull ProblemView problem) {
        var questionViews = getQuestionViews(problem.getProblemId());
        for (var questionView : questionViews) {
            loadQuestionViewDetails(questionView);
        }
        problem.setQuestions(questionViews);
    }
    //endregion

    // region collection problem
    public CollectionProblem addCollectionProblem(@Min(0) long collectionId, @Min(0) long partId, @Min(0) long sectionId, @Min(0) long problemId, long operatorId) {
        var existedCollectionProblem = problemCollectionRepository.getCollectionProblemByProblem(problemId);
        if (Objects.nonNull(existedCollectionProblem)) {
            ExceptionUtils.throwServiceException(ProblemsErrorCodes.COLLECTION_PROBLEM_EXISTS, problemId);
        }

        var collectionProblem = new CollectionProblem();
        collectionProblem.setCollectionId(collectionId);
        collectionProblem.setProblemId(problemId);
        collectionProblem.setProblemIndex(problemCollectionRepository.getMaxProblemIndex(collectionId) + 1);
        collectionProblem.setPartId(partId);
        collectionProblem.setSectionId(sectionId);

        return problemCollectionRepository.saveCollectionProblem(collectionProblem);
    }

    public boolean removeCollectionProblem(@Min(0) long collectionId, @Min(0) long problemId, long operatorId) {
        var existedCollectionProblem = problemCollectionRepository.getCollectionProblemByProblem(collectionId, problemId);
        if (Objects.nonNull(existedCollectionProblem)) {
            // resort problem index
            int currentIndex = existedCollectionProblem.getProblemIndex();
            problemCollectionRepository.resortProblemIndex(collectionId, currentIndex);
            // remove problem from collection
            problemCollectionRepository.deleteCollectionProblem(existedCollectionProblem.getId());
        }
        return true;
    }
    // endregion

    public long getExamIdByProblem(@Min(0) long problemId) {
        return problemViewMapper.findExamIdByProblem(problemId);
    }
}
